#  Public and for release, CBEC v3.1.06 Build 4255+ [10th November, 2025]
#
#  Copyright(c) Ivyware Pty Ltd 2019-25  (all rights reserved)
#               MELBOURNE, VICTORIA, AUSTRALIA, 3000
#
#  This file is provided as-is by Ivyware Pty Ltd.  No claims are made
#  as to fitness for any particular purpose.  No warranties of any kind
#  are expressed or implied.  The recipient agrees to determine
#  applicability of information provided.
#
#  Ivyware hereby grants the right to freely use the information
#  supplied in this file for the creation of Python automation scripts
#  supporting the Chartboard Application, and to make copies of this
#  file in any form for internal or external distribution as long as
#  this notice remains attached.
#
#  No waranty or suitability for purpose is implied.
#
#  Simple Python Scanner script using the MACD indicator to generate
#  a list of stocks with MACD buy signals at values less than zero
#  NOTES: Download latest version from
#         https://www.ivyware.com.au/SampleScanner[MACD].pys
#       : Download latest version of the Chartboard Extension Classes
#         (PythonCBEC.pyw) upon which this script is based from
#         https://www.ivyware.com.au/PythonScripts/PythonCBEC.pyw
#       : Script interacts with desktop ribbon bar via selected values for
#         the "Period Units" and "Lookback Periods".
#       : Requirement is for phython 3.8 to be installed
#       : Based upon Chartboard Extension Classes (CBEC) shipped with
#         Chartboard product.  Script only runs as client of Chartboard.
#       : Script is run once for each stock encountered by the scanner,
#         state is not maintained between runs.  Selection status MUST
#         be confirmed for positive scans
#       : Scanning scripts are allocated the *.pys extension, advisor scripts
#         the *.pya extension and common scripts the *.pyw extension.
#

import sys
sys.path.insert(0, 'C:\\Program Files\\Chartboard\\PythonScripts') # CBEC directory
from PythonCBEC import *
import ctypes  # An included library with Python install.   
from datetime import datetime
from PYCB import PYCB_return
from PYCB import PYCB_error

#
#  Environment variables
#  NOTES: List of current environment variables suitable for debugging
import os
#print ( 'Python environment')
#for param in os.environ.keys():
#    print ( "%20s %s" % (param,os.environ[param]) )
#
#   STEP 1 - Establish Root from which all other objects are descendants
#   NOTES: Effectively the Chartboard application itself.  This script
#          is coded to run on CScanOHLCvs type views only.
#        : The parent window 'CView' refers to collection of tabbed views
#          in the centre of the application, 'this' refers to current
#          Chartboard view with focus from which script has been activated.
oCRoot = CRoot();
#
#   STEP 2 - Establish CView
#   NOTES: CView is the parent of all the tabbed view types. Effectively
#          the View tab under which this python script is running and
#          identified by the 'this' tag
#        : Confirm 'CScanOHLCvs' type view, script only supports such
oCView = oCRoot.CViewFactory('this');
if not oCView.IsCScanOHLCvs():
    raise PYCB_error("Script only supports CScanOHLCvs type CView's, not " + oCView.sType)
#
#   STEP 3 - Establish the CScanOHLCvs object
#   NOTES: Must be a scan type View tab for this script to work.  Fails for
#          all other view types.  Script may only access single scan tab.
#        : Scan Period Units controlled by the current Ribbon Bar >> Scan Group
#          >> Scan Period Units tab selection.  Alternatively this value may be
#          over-ridden via PUnits_Year, Quarter, Month, Week or Day definition
#          from PythonCBEC.pyw
#        : Calculations are performed and values persist concurrently for all
#          Period Units.  Hence, state for alternative periods can be compared.
oCScanOHLCvs = oCView.CScanFactory();
oCScanOHLCvs.Setenvar_i('PUnits', PUNITS_Month )
#oCScanOHLCvs.Setenvar_i('LBPeriods', 8 )
oCScanOHLCvs.Refresh()                    # Refreshes cached values
#
#   STEP 4 - Access the CStackOHLCvs object within oCScanOHLCvs
#   NOTES: The ScanOHLCvs tab builds a synthetic chart for encountered
#          stocks containing all charts and overlays referenced as
#          Prerequisites in this script.
#        : Chart reference order is respected.  Chart can be viewed
#          through context menu of selected stocks list control in the
#          ScanView
#        : Some of the Prerequisites are for demonstration and cosmetic
#          purposes only.  Firstly the chart stack is defined as a list of
#          prerequisites.
oCStackOHLCvs = oCScanOHLCvs.CStackFactory()
oCStackOHLCvs.Prerequisites('RSI')
oCStackOHLCvs.Prerequisites('OHLCvs')
oCStackOHLCvs.Prerequisites('MACD')
oCStackOHLCvs.Prerequisites('SLOPE')
oCScanOHLCvs.SetSelected ( 0 )         # Not-selected as default summary
#
#   STEP 5 - Configuration of Charts and Overlays within stack
#   NOTES: Chart decorations that may or may not be referenced by scan logic
#        : Secondly the Prerequisite overlays are defined for each chart.
if oCStackOHLCvs.ChartExists('RSI'):
    oChartRSI = oCStackOHLCvs.ChartFactory('RSI')
    oChartRSI.Prerequisites('RSI')
    oDSeriesRSI = oChartRSI.DSeriesFactory('RSI')
    oDSeriesRSI.SetConfig_i('Signaline',1)
    oDSeriesRSI.SetConfig_i('SignalineSMA',1)
if oCStackOHLCvs.ChartExists('MACD'):
    oChartMACD = oCStackOHLCvs.ChartFactory('MACD')
    oChartMACD.Prerequisites('MACD')
    oDSeriesMACD = oChartMACD.DSeriesFactory('MACD')
    oDSeriesMACD.SetConfig_i('SignalBUY',1)
    oDSeriesMACD.SetConfig_i('SignalSELL',1)

#   STEP 6 - Position cursor at reference date
#   NOTES: Reference date is controlled by the Ribbon Bar >> Scan Group
#          >> Reference Date selection.  Alternatively this value may be
#          over-ridden via '[x] RefDate Today'
dRefDateSet  = oCScanOHLCvs.SetCursorPos(oCScanOHLCvs.dRefDATE,oCScanOHLCvs.nPUnits)
nDATEmin     = oCScanOHLCvs.GetDSetDATE('DATEmin',oCScanOHLCvs.nPUnits)
nDATEmax     = oCScanOHLCvs.GetDSetDATE('DATEmax',oCScanOHLCvs.nPUnits)
nRefDateDiff = P2Helpers.P2PUnits_Diff ( oCScanOHLCvs.dRefDATE, nDATEmin, oCScanOHLCvs.nPUnits )
if nRefDateDiff <= oCScanOHLCvs.nLBPeriods :
    print ( 'No data exists within loop back periods=' + str(nRefDateDiff) )
    raise PYCB_return
dRefDATEset = oCScanOHLCvs.SetCursorPos(oCScanOHLCvs.dRefDATE,oCScanOHLCvs.nPUnits)
nCursorPos  = oCScanOHLCvs.GetCursorPos(oCScanOHLCvs.nPUnits)

#################################################
#   STEP 7 - Scan Logic
#   NOTES: Only select those charts with bullish MACD crossovers < nLBPeriods
#        : dMACD - Calculated MACD value
#          nBoS  - Buy(true) or Sell(false) state
#          nBoSage - Age of buy/sell state in lookback periods
#
if ( oCStackOHLCvs.ChartExists('MACD') and
     nRefDateDiff >= oCScanOHLCvs.nLBPeriods ):
    oChartMACD = oCStackOHLCvs.ChartFactory('MACD')
    oChartMACD.Prerequisites('MACD')
    oDSeriesMACD = oChartMACD.DSeriesFactory ('MACD')
    oCScanOHLCvs.SetCursorPos(oCScanOHLCvs.dRefDATE,oCScanOHLCvs.nPUnits)
    dMACD   = oDSeriesMACD.GetValue_d('MACD',oCScanOHLCvs.nPUnits,0)
    nBoS    = oDSeriesMACD.GetValue_i('BoS',oCScanOHLCvs.nPUnits,0)
    nBoSage = oDSeriesMACD.GetValue_i('BoSage',oCScanOHLCvs.nPUnits,0)
    print ( 'CScanOHLCvs LBPeriods=' + str(oCScanOHLCvs.nLBPeriods) )
    if ( nBoS is not None and nBoS > 0 and
         nBoSage is not None and nBoSage <= oCScanOHLCvs.nLBPeriods and
         dMACD is not None and dMACD < 0.0 ):
        print ( 'Selected==============================' )
        oCScanOHLCvs.SetSelected ( True ) # Flags this stock/chart as selected
        sComments = 'MACD < 0; MACD buy signal <= ' + str(oCScanOHLCvs.nLBPeriods) + oCScanOHLCvs.sPUnits
        oCScanOHLCvs.Setenvar_s('Comments', sComments )



